from ConfigUtils import parse_config
import sys 

import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.gui.OnscreenText import OnscreenText
from direct.gui.OnscreenImage import OnscreenImage
from direct.interval.IntervalGlobal import *
from direct.task.Task import Task
from ConfigUtils import parse_config
import sys
from random import choice, randrange
from direct.gui.DirectGui import *
from pandac.PandaModules import *
from PhotoUtils import *
from GameFSM import *
from Character import *


WORLD_WIDTH=10
WORLD_HEIGHT=10


label_font = loader.loadFont("fonts/FreeSans.ttf")
g_mouseover_sound = loader.loadSfx("sounds/mouseover.wav")
g_mouseclick_sound = loader.loadSfx("sounds/mouseclick.wav")


class World(DirectObject):
  def __init__(self):
    """
    Generate the gallery world.
    """
    self.world = render.attachNewNode("world")

    '''Initialization'''
    self.create_floor()
    self.load_pictures()
    self.score = 0
    self.create_hud()
    self.isMouse3Clicked = False
    self.xMouse3Clicked = 0
    self.viewing2d = False
    self.mute = True
    self.oldNPKey = 0 #Key for NP
    base.disableMouse()
    self.countEmpty = 0
    self.oldMouseX = 0
    self.oldMouseY = 0
    self.clickednp = None
    self.isQuestion = False
    self.mouseOverPicNP = None
    self.question = None
    '''Setting up the camera, '''
    base.camera.setPos(0,-250, 50)
    base.camera.lookAt(0,0,0)
    
    '''Defining what keys are accepted'''
    # Movement keys
    key_map = (('a',['left',1]),('a-up',['left',0]),('d',['right',1]),('d-up',['right',0]),
              ('arrow_left',['left',1]),('arrow_left-up',['left',0]),
              ('arrow_right',['right',1]),('arrow_right-up',['right',0]))

    # Set up movement keys (AD)
    # This will store what is currently being pressed
    self.keystate = {'right':0, 'left':0}
    # This will update the information based on the keyboard input
    for key in key_map:
        self.accept(key[0],self.set_keystate,key[1])

    self.accept('w', base.toggleWireframe)
    self.accept('m', self.toggleMute)
    self.accept('mouse1', self.mouseClickedPic)
    self.accept('escape',sys.exit)
    self.accept('space',self.beginGame)

    self.accept('e',self.gamelost)
    self.accept('r',self.gamewon)

    
    #Answer choices
    for i in range(1,9):
      self.accept(str(i), self.answerChoice_handler, [str(i)])

    '''Ambient and light created'''
    ambient_source = AmbientLight('ambient')
    ambient_source.setColor(Vec4(0.7,0.7,0.7,1))
    ambientnp = self.world.attachNewNode(ambient_source.upcastToPandaNode())
    self.world.setLight(ambientnp)

    lens = PerspectiveLens()
    lens.setNearFar(0.1, 27)
    spot_source = Spotlight('spotlight')
    spot_source.setColor(Vec4(0.6,0.6,1.0,1))
    spot_source.setAttenuation(Vec3(0,0,0))
    #spot_source.showFrustum() # Uncomment for great debug information
    spot_source.setLens(lens)
    self.spotnp = self.world.attachNewNode(spot_source.upcastToLensNode())      
    self.floor.setLight(self.spotnp)
    self.spotnp.setPos(Vec3(-1000,-1000,-1000))
    self.spotnp.setHpr(0,0,0)

    '''Creating a Traverser and a CollisionRay, to select pictures'''
    self.myTraverser = CollisionTraverser()
    self.myHandler = CollisionHandlerQueue()

    '''Make a collision node for our picker ray'''
    self.pickerNode=CollisionNode('mouseRay')

    '''Attach that node to the camera since the ray will need to be positioned relative to it'''
    self.pickerNP=camera.attachNewNode(self.pickerNode)
    self.pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
    '''Everything to be picked will use bit 1. This way if we were doing other collision we could seperate it'''
    #self.pickerNode.setFromCollideMask(BitMask32.bit(1))

    self.pickerRay=CollisionRay()
    self.pickerNode.addSolid(self.pickerRay)

    '''Register the ray as something that can cause collisions'''
    self.myTraverser.addCollider(self.pickerNP, self.myHandler)

    """Sound"""
    self.soundRestore = loader.loadSfx("sounds/Windows XP Restore.wav")
    self.soundMinimize = loader.loadSfx("sounds/Windows XP Minimize.wav")
    self.soundOver = loader.loadSfx("sounds/Windows.wav")
    self.soundExplode = loader.loadSfx("sounds/bomb-06.wav")
    self.soundEarthQuake = loader.loadSfx("sounds/earthquake.wav")
    self.soundRestore.setVolume(1)    
    self.soundMinimize.setVolume(1)
    self.soundOver.setVolume(0.3)
    self.soundExplode.setVolume(0.1)

    '''Tasks checking if moue is colliding with a pictuer and updating the camera when rotating '''
    self.lasttime = 0
    taskMgr.add(self.mouseTask, "mouseTask")
    taskMgr.add(self.update_camera, "camerathread") # Start a new thread which then will loop
                                                    # forever to update the camera position

    self.char = Character(self.world)
    self.create_skybox()
    self.gameFSM = GameFSM(self)

    render.setShaderAuto()
    
    self.buttons = []
    #self.create_answer_buttons()
    
  def beginGame(self):
    if not self.introNodePath:
      return
    
    #print "Pick a picture!"
    self.introNodePath.detachNode()
    self.introNodePath = None
    
    '''Characters enters idle state'''
    messenger.send('idle')
    
    
  
  def answerChoice_handler(self, arg):
    choice = int(arg[0])-1
    #print( "The choice: %d was picked." % choice )
    
    #if answer is correct for this picture
    #right_answer = '1'
    if self.question.answers[choice].isCorrect():
      messenger.send('correct')
    else:
      messenger.send('wrong')
    
    
  def doWelcome(self):
    #intro
    intro = "Welcome stranger!\n\n"
    intro += "You must now take my test! "
    intro += "If you fail this test, I will destroy the world! "
    intro += "On the other hand, if you pass, I will dance with joy!\n\n"
    intro += "You must select a picture and answer a question about it.\n"
    intro += "If you give 5 wrong answers in row you loose!\n"
    intro += "If you get 5 correct answers in a row, you win!\n\n"
    intro += "press space to continue..."

    text = TextNode('intro')
    text.setText(intro)
    text.setShadow(0.05, 0.05)
    text.setShadowColor(0, 0, 0, 1)
    text.setWordwrap(30.0)
    text.setAlign(TextNode.ALeft)
    text.setCardColor(0.1, 0.1, 0.1, 0.3)
    text.setCardAsMargin(1,1,1,1)
    text.setFont(label_font)
    text.setTextColor(1,1,1,1)
    text.setFrameColor(0.5,0.5,0.5,1)
    text.setSlant(0.3)
    self.introNodePath = aspect2d.attachNewNode(text)
    self.introNodePath.setScale(0.07)
    self.introNodePath.setPos(-1,-1,0)
  
    
    
    
    
  def doQuestion(self):
    
    self.isQuestion = True

    questions = self.mouseOverPicNP.getPythonTag('questions')
    self.question = choice(questions)
    
    #pick question for the picture selected
    #print "what color is the sky on this picture?"
    #print "1. blue"
    #print "2. green"
    #print "3. red"

    #print( self.question.text )
    #for a in self.question.answers:
      #print( " %s (%s)" % (a.text, str(a.isCorrect())) )

    
    self.create_question_gui()   
    
         
  def create_question_gui(self):
    self.create_question()
    self.create_answer_buttons()


  def create_question(self):
    text = TextNode('quiz')
    text.setText(self.question.text)
    text.setShadow(0.05, 0.05)
    text.setShadowColor(0, 0, 0, 1)
    text.setWordwrap(30.0)
    text.setAlign(TextNode.ALeft)
    text.setCardColor(0.1, 0.1, 0.1, 0.3)
    text.setCardAsMargin(0, 0, 0, 0)
    text.setFont(label_font)
    text.setTextColor(1,1,1,1)
    text.setFrameColor(0.5,0.5,0.5,0.75)
    self.textNodePath = aspect2d.attachNewNode(text)
    self.textNodePath.setScale(0.07)
    self.textNodePath.setPos(-0.80,0,-0.2)
    
    
  def create_answer_buttons(self):
      
    startPosY = self.textNodePath.getZ() #its really the Y pos...
    startPosX = self.textNodePath.getX()

    l = 0
    '''finding max size on buttons'''
    for answer in self.question.answers:
      x = len(answer.text)
      if x > l:
        l = x
        
      if l > 25:
        l = l * 0.5
      elif l < 10:
        l = l * 1.2
    
    size = l/2
    
    x=1
    for answer in self.question.answers:
      text = str(x) + ". " + answer.text
      
      bgen = DirectButton(text_fg=(1,1,1,1), text_shadow=(0,0,0,1), frameColor=(0.4,0.5,0.4,0.75), borderWidth=(0.1,0.1), relief=DGG.RAISED, 
        text=(text,text,"> " + text + " <",""), pressEffect=1, scale=0.05, frameSize=(-(size),(size),-0.75,1.5), 
        command=self.answerChoice_handler, extraArgs=[str(x)], rolloverSound = g_mouseover_sound, clickSound = g_mouseclick_sound)
      

      if x == 1:
        start = startPosY-0.1
     
        
      bgen.setPos(startPosX,0,start+(-0.15*(x)))
      self.buttons.append(bgen)
      x = x + 1
      
    
  def doCorrectAnswer(self):
    self.isQuestion = False
    self.move_pic_on_wall()
    self.remove_question_gui()
    #print "Correct answer!"
    if self.score < 0:
      self.score = 0
    self.score = self.score+1
    self.updateScore()
    if self.score == 5:
      self.gamewon()
    
    
  def doWrongAnswer(self):
    self.isQuestion = False
    self.move_pic_on_wall()
    self.remove_question_gui()
    #print "Wrong answer!"
    if self.score > 0:
      self.score = 0
    self.score = self.score-1
    self.updateScore()
    if self.score == -5:
      self.gamelost()


  def remove_question_gui(self):
    self.textNodePath.detachNode()
    for btn in self.buttons:
      btn.destroy()

  def create_skybox(self):
    self.skybox = loader.loadModel("models/skybox.egg")
    self.skybox.reparentTo(self.world)
    self.skybox.setScale(500)


  def toggleMute(self):
    if self.mute:
      self.hud_sound.setText("[m]ute: off")
      self.mute = False      
    else:
      self.hud_sound.setText("[m]ute: on")
      self.mute = True


  def set_keystate(self, key, state):
    """ Set a stored state (either 0 or 1) for a specified key ('back',...)"""
    self.keystate[key] = state # Updates the member variable holding the states of the keys


  def update_camera(self, task):
    """
    Update camera position and/or rotation for all active key states.
    """
    elapsed = task.time - self.lasttime

    if self.keystate['right'] is 1:
        self.floor.setH(self.floor.getH()-60*elapsed)
    if self.keystate['left'] is 1:
        self.floor.setH(self.floor.getH()+60*elapsed)

    self.lasttime = task.time

    rot = self.floor.getH()
    self.textnp1.setH(-rot)    
    self.textnp2.setH(-rot)    
    self.textnp3.setH(-rot)    
    self.textnp4.setH(-rot)    
    return task.cont


  def create_floor(self):
    cm=CardMaker('')
    cm.setFrame(0, 10, 0, 10)
    self.floor = self.world.attachNewNode("floor")
    floorTex = loader.loadTexture('textures/floor_lightgrey.png')
    height = (WORLD_HEIGHT/2)*-10
    for h in range(WORLD_HEIGHT):
      width = (WORLD_WIDTH/2)*-10
      for w in range(WORLD_WIDTH):
        floor_tile = self.floor.attachNewNode(cm.generate())
        floor_tile.setPos(width, height,-0.5)
        floor_tile.setP(-90)
        width = width + 10
        floor_tile.setTexture(floorTex)
      height = height + 10
    self.textnp1 = self.create_label("Buildings", VBase3(0,-50,1), self.floor)    
    self.textnp2 = self.create_label("Cars", VBase3(50,0,1), self.floor)    
    self.textnp3 = self.create_label("Cartoons", VBase3(0,50,1), self.floor)    
    self.textnp4 = self.create_label("Movies", VBase3(-50,0,1), self.floor)


  def create_label(self, text, pos, parent):
    textn = TextNode(text)
    textn.setCardColor(0.1,0.1,0.1, 0.3)
    textn.setCardDecal(1)
    textn.setCardAsMargin(0.3,0.3,0.1,0.1)
    textn.setAlign(TextNode.ACenter)
    textn.setFont(label_font)
    textn.setTextColor(1,1,1,1)
    textn.setFrameColor(0.5,0.5,0.5,1)
    textnp = parent.attachNewNode(textn)
    textnp.setPos(pos)
    textnp.setScale(2)
    textn.setText(text)
    return textnp


  def load_pictures(self):
    parse_config(self.floor)


  def create_hud(self):
    hud_quit = OnscreenText(pos = (-1.3, 0.9), fg = (1,1,1,1), bg = (0.3, 0.3, 0.3, 0.5),
                            align = TextNode.ALeft, scale = 0.04, mayChange = True)
    self.hud_sound = OnscreenText(pos = (-1.3, 0.85), fg = (1,1,1,1), bg = (0.3, 0.3, 0.3, 0.5),
                                  align = TextNode.ALeft, scale = 0.04, mayChange = True)
    hud_control = OnscreenText(pos = (-1.3, 0.75), fg = (1,1,1,1), bg = (0.3, 0.3, 0.3, 0.5),
                               align = TextNode.ALeft, scale = 0.04, mayChange = True)
 
    hud_quit.setText("[Esc] to quit")    
    self.hud_sound.setText("[m]ute: on")
    hud_control.setText("Use A,D or arrow keys to rotate")

    self.hud_score = OnscreenText(pos = (1.0, 0.85), fg = (1,1,1,1), bg = (0.3, 0.3, 0.3, 0.5),
                                  align = TextNode.ARight, scale = 0.06, mayChange = True)
    #self.hud_score.setText("Neutral")
    self.updateScore()


  def updateScore(self):
    if self.score == 0:
      self.hud_score.setText("")

    if self.score > 0:
      self.hud_score.setText("%d correct answers" % self.score)
      self.hud_score.setFg((0,1,0,1))
    if self.score == 1:
      self.hud_score.setText("one correct answer")

    if self.score < 0:
      self.hud_score.setText("%d wrong answers" % -self.score)
      self.hud_score.setFg((1,0,0,1))
    if self.score == -1:
      self.hud_score.setText("one wrong answer")


  '''Task function, a picture gets highlighted'''
  def mouseTask(self, task):
    
    if self.char.state == "GameWon":
      return
    
    if base.mouseWatcherNode.hasMouse() == False:
      return Task.cont

    '''not checking for collision when rotating'''
    if self.keystate['right'] is 1 or self.keystate['left'] is 1:
      return Task.cont

    x = base.mouseWatcherNode.getMouseX()
    y = base.mouseWatcherNode.getMouseY()

    '''just checking collision once each x,y coordinate'''
    if x == self.oldMouseX and y == self.oldMouseY:
      return Task.cont

    self.oldMouseX = x
    self.oldMouseY = y

    np = self.findCollidePic()

    if not np.isEmpty():
      self.mouseOverPic(np)
      self.countEmpty = 0
    else:
      self.countEmpty = self.countEmpty + 1

    '''if collision detection is empty 3 times in a row then put spotlight out of sight'''
    '''possible to not detect a picture tho the mouse is over it'''
    if self.countEmpty > 2:
      self.countEmpty = 0
      if self.spotnp.getPos() != Point3(-1000,-1000,-1000):
        self.spotnp.reparentTo(self.world)
        self.spotnp.setPos(-1000,-1000,-1000)
        self.mouseOverPicNP = None

    return Task.cont


  '''When mouse is over a picture'''
  def mouseOverPic(self, nodepath):
    
    if self.isQuestion:
      return
    
    if not self.mouseOverPicNP and self.mouseOverPicNP == nodepath:
      return

    
    '''When still on the same picture we want to keep hold of that picture'''
    self.mouseOverPicNP = nodepath


    if not self.mute:
      self.soundOver.play()
      base.sfxManagerList[0].update()

    self.spotnp.reparentTo(nodepath)
    self.spotnp.setPos(0,-20,0)   


  def mouseClickedPic(self):
    
    if self.char.state == "GameWon":
      return
    
    
    '''not allowed to click a picture when in question mode'''
    if self.isQuestion:
      return
    
    '''If we don't have the picture from mouseOver task then exit'''
    if not self.mouseOverPicNP:
      return
    
    
    '''start question'''
    messenger.send('proceed')
    
    '''another way to turn the spotlight off?'''
    self.spotnp.setPos(-1000,-1000,-1000)
    self.spotnp.reparentTo(self.world)
    
    
    self.clickednp = self.mouseOverPicNP #self.clickednp is used in movePictureOnWall()
    self.prevparent = self.clickednp.getParent()
    self.prevpos = self.clickednp.getPos()
    self.prevhpr = self.clickednp.getHpr()

    self.clickednp.wrtReparentTo(render)

    moveIn = LerpPosHprInterval(self.clickednp, 1.0, VBase3(-2,-230,46), VBase3(0,-11,0),
                                blendType = 'easeInOut')#, startPos, startHpr)
    moveIn.start()
   
  
  
  def move_pic_on_wall(self):
    if not self.clickednp:
      return
    
    moveBack = LerpPosHprInterval(self.clickednp, 1.0, self.prevpos, self.prevhpr,
                                  blendType = 'easeInOut')#, startPos, startHpr)
    self.clickednp.wrtReparentTo(self.prevparent)
    
    '''Spotlight will position it self in front of the picture when it has returned'''
    Sequence(moveBack, Func(self.spotnp.reparentTo, self.clickednp), Func(self.spotnp.setPos, Point3(0,-20,0))).start()
    self.clickednp = None
    self.isQuestion = False
    


  '''Finds if the mouse collides with a picture, returns nodepath'''
  def findCollidePic(self):
    np = NodePath()
    mpos=base.mouseWatcherNode.getMouse()

    '''This makes the ray's origin the camera and makes the ray point
    to the screen coordinates of the mouse'''
    self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())

    self.myTraverser.traverse(render)
    '''Assume for simplicity's sake that myHandler is a CollisionHandlerQueue'''
    if self.myHandler.getNumEntries() > 0:
     self.myHandler.sortEntries() #this is so we get the closest object
     np=self.myHandler.getEntry(0).getIntoNodePath()

     np=np.findNetTag("pw")

    return np

  def gamewon(self):
    '''Wizard dances happily'''
    messenger.send('won')
    self.spotnp.reparentTo(self.world)
    self.spotnp.setPos(-1000,-1000,-1000)
    self.mouseOverPicNP = None

  def gamelost(self):
    '''Wizard destroys the world'''
    messenger.send('lost')
    
    x=self.floor.getX()
    y=self.floor.getY()
    z=self.floor.getZ()
    dx = randrange(-3,3)
    dy = randrange(-3,3)
    move1 = LerpPosInterval(self.floor, duration=0.05, pos=Point3(x+dx,y+dy,z))
    dx = randrange(-3,3)
    dy = randrange(-3,3)
    move2 = LerpPosInterval(self.floor, duration=0.05, pos=Point3(x+dx,y+dy,z))
    dx = randrange(-3,3)
    dy = randrange(-3,3)
    move3 = LerpPosInterval(self.floor, duration=0.05, pos=Point3(x+dx,y+dy,z))
    dx = randrange(-3,3)
    dy = randrange(-3,3)
    move4 = LerpPosInterval(self.floor, duration=0.05, pos=Point3(x+dx,y+dy,z))
    self.soundEarthQuake.play()
    shake = Sequence(move1,move2,move3,move4).loop()
    #self.explode()
    taskMgr.doMethodLater(2.0, self.explode, 'Explode')


  def explode(self, task):
    explode = Parallel(SoundInterval(self.soundExplode))
    objects = self.world.findAllMatches('floor/**/*')
    for i in range(0, objects.getNumPaths()):
      o = objects[i]
      o.reparentTo(render)
      p = LerpPosInterval( o, 6.0, VBase3(randrange(-2000,2000),
                                          randrange(-2000,2000),
                                          randrange(-2000,2000)))
      explode.append(p)
    explode.start()